/**
 * build_project
 * <p>
 * Copyright 2014 cayden.cn, Inc. All rights reserved.
 *
 * @author Administrator
 * @date 2021-06-21 09:04
 */
package com.cayden.config.boot.listener;


import ch.qos.logback.classic.LoggerContext;
import com.cayden.base.exception.AppConfigException;
import com.cayden.base.exception.CaydenBaseException;
import com.cayden.base.utils.ShutdownHooks;
import com.cayden.base.utils.SystemUtils;
import com.cayden.config.boot.ApplicationContextHolder;
import com.cayden.config.boot.CaydenBanner;
import com.cayden.config.boot.CaydenSpringbootApplication;
import com.cayden.config.boot.Apps;
import com.cayden.config.init.DefaultLogPath;
import com.cayden.config.log.CaydenLogbackLoggingSystem;
import com.cayden.config.log.initializer.ConsoleLogInitializer;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringApplicationRunListener;
import org.springframework.boot.logging.LoggingSystem;
import org.springframework.boot.system.ApplicationPid;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.util.Assert;

import java.util.List;
import java.util.Set;

/**
 * @author cayden
 * @date 2021-06-21 09:04
 */
@Slf4j
public class CaydenApplicationRunListener implements SpringApplicationRunListener {

    public static final String COMPONENTS_PACKAGE = "com.cayden.module";

    private SpringApplication application;

    private String[] args;

    private static List<String> disabledPackageName =
            Lists.newArrayList("", "com.cayden", "com.cayden.core", "com.cayden.core.common.boot",
                    COMPONENTS_PACKAGE);


    public CaydenApplicationRunListener(SpringApplication application, String[] args) {
        this.application = application;
        this.args = args;
        this.setThreadName();
        CaydenSpringbootApplication bootApp = this.findBootApplication(application);
        // 织入banner 更帅气
        application.setBanner(new CaydenBanner(bootApp));
        this.initEnvVars(bootApp);
    }

    private void initEnvVars(CaydenSpringbootApplication bootApp) {
        String sysName = bootApp.sysName();
        Assert.hasLength(sysName, "系统名不能为空");
        System.setProperty("cayden.appName", sysName);
        System.setProperty("cayden.application.name", sysName);
        System.setProperty("server.servlet.application-display-name", sysName);
        System.setProperty("server.server-header", "CAYDEN");
        System.setProperty("server.port", Integer.toString(bootApp.runningPort()));
        System.setProperty("spring.profiles.active",bootApp.environment().name());

        // init base package info
        String basePackage = bootApp.initBasePackage();
        String enableInitBasePackage = bootApp.enableInitBasePackage();
        if (enableInitBasePackage.equalsIgnoreCase("true")){
            String osName = SystemUtils.getOsName();
            if (osName.equals("mac")){
                String userDir = System.getProperty("user.dir");
                Apps.initDefaultBasePackage(userDir + DefaultLogPath.MAC_OS_EVN_BASE_PACKAGE);
                System.setProperty("cayden.log.path", userDir + DefaultLogPath.MAC_OS_EVN_BASE_PACKAGE);
            }else {
                Apps.initDefaultBasePackage(DefaultLogPath.WIN_AND_LINUX_BASE_PACKAGE);
                System.setProperty("cayden.log.path", DefaultLogPath.WIN_AND_LINUX_BASE_PACKAGE);
            }
        }else {
            Apps.initDefaultBasePackage(basePackage);
            System.setProperty("cayden.log.path", basePackage);
            System.setProperty("spring.devtools.restart.enabled", "false");
        }


        if (bootApp.environment().name().equals("dev") || bootApp.environment().name().equals("local") || bootApp.environment().name().equals("sdev")){
            System.setProperty(Apps.DEV_MODE_KEY,"true");
        }
        System.setProperty("redis.auto.close.status",Integer.toString(bootApp.redisAutoClose()));
        System.setProperty(LoggingSystem.SYSTEM_PROPERTY, CaydenLogbackLoggingSystem.class.getName());
        System.setProperty("spring.aop.proxy-target-class", Boolean.TRUE.toString());




        Runnable var10001 = new Runnable() {
            @Override
            public void run() {
                System.setProperty("cayden.pid", (new ApplicationPid()).toString());
            }
        };

    }

    private CaydenSpringbootApplication findBootApplication(SpringApplication application) {
        Class sourceClass = (Class) application.getAllSources().stream().map((o1) -> {
            return (Class) o1;
        }).filter((o1) -> {
            return AnnotationUtils.findAnnotation(o1, CaydenSpringbootApplication.class) != null;
        }).findFirst().orElseThrow(() -> {
            return new RuntimeException("启动类必须标注" + CaydenSpringbootApplication.class.getSimpleName());
        });
        return (CaydenSpringbootApplication) AnnotationUtils.findAnnotation(sourceClass, CaydenSpringbootApplication.class);
    }

    private void setThreadName() {
        Thread.currentThread().setName("main");
    }

    @Override
    public void starting() {
        checkAndSetPackage(application);
    }

    @Override
    public void environmentPrepared(ConfigurableEnvironment environment) {

    }

    @Override
    public void contextPrepared(ConfigurableApplicationContext context) {

    }

    @Override
    public void contextLoaded(ConfigurableApplicationContext context) {

    }

    @Override
    public void started(ConfigurableApplicationContext context) {
        new ShutdownThread().register();
    }

    @Override
    public void running(ConfigurableApplicationContext context) {

    }

    @Override
    public void failed(ConfigurableApplicationContext context, Throwable exception) {
        // 对于启动失败的情况,要做日志输出
        LoggerFactory.getLogger(CaydenApplicationRunListener.class).error("启动失败: {}", exception.toString(), exception);
        ShutdownHooks.shutdownAll();
    }

    private void checkAndSetPackage(SpringApplication application) {
        Set<Object> allSources = application.getAllSources();
        for (Object allSource : allSources) {
            Package pkg = ( (Class) allSource ).getPackage();
            // 为了spring完美支持，扫描内部包，所以禁止外部main设为这个名称
            if (pkg == null || disabledPackageName.contains(pkg.getName())){
                throw new AppConfigException("请把main-class 设置到package中");
            }
            System.setProperty(Apps.BASE_PACKAGE, pkg.getName());
        }

    }

    static class ShutdownThread extends Thread {

        public ShutdownThread() {
            super("Shutdown");
        }

        @Override
        public void run() {
            shutdownApp();
        }

        public void register() {
            Runtime.getRuntime().addShutdownHook(this);
        }
    }

    /**
     * 关闭应用
     */
    public static void shutdownApp() {
        Logger logger = LoggerFactory.getLogger(CaydenApplicationRunListener.class);
        logger.info("应用[{}]开始关闭", Apps.getAppName());
        //在应用关闭时打印console log,便于自动化发布系统查看日志
        ConsoleLogInitializer.addConsoleAppender();
        //close spring container
        ApplicationContext applicationContext = ApplicationContextHolder.get();
        if (applicationContext instanceof ConfigurableApplicationContext) {
            if (!( (ConfigurableApplicationContext) applicationContext ).isActive()) {
                System.exit(0);
            } else {
                ( (ConfigurableApplicationContext) applicationContext ).close();
            }
        }
        ShutdownHooks.shutdownAll();
        shutdownLogSystem();
    }

    private static void shutdownLogSystem() {
        //关闭日志，日志需要最后关闭,等所有资源清理后关闭日志
        LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
        loggerContext.stop();
    }
}
